﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading;

namespace RouteHelper
{
    public partial class frmRouteHelper : Form
    {
        /// <summary>
        /// File path of "route.exe".
        /// </summary>
        const string ROUTE_PATH = "C:\\Windows\\System32\\route.exe";

        /// <summary>
        /// Regular expression for finding route entries in the output text of "route print".
        /// </summary>
        const string RGX_ROUTE_ENTRIES = @"(?<Destination>(\d+\.){3}\d+)\s+(?<Netmask>(\d+\.){3}\d+)\s+(?<Gateway>((\d+\.){3}\d+)|(On-link))\s+(?<Interface>(\d+\.){3}\d+)\s+(?<Metric>\d+)";

        bool _isModified = false;
        string _filePath = null;
        bool _enableFunctions;

        /// <summary>
        /// A deleget which is used for invoke action.
        /// </summary>
        delegate void Invokee();

        public frmRouteHelper()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Gets or sets a value indicating whether the main functions are enabled.
        /// </summary>
        /// <value>
        ///   <c>true</c> if the main functions are enabled; otherwise, <c>false</c>.
        /// </value>
        public bool EnableFunctions
        {
            get
            {
                return _enableFunctions;
            }
            set
            {
                tlpFunctions.Enabled = value;
                mnuOpen.Enabled = mnuSave.Enabled = mnuSaveAs.Enabled = value;
                _enableFunctions = value;
            }
        }

        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Form.Closing"/> event.
        /// </summary>
        /// <param name="e">A <see cref="T:System.ComponentModel.CancelEventArgs"/> that contains the event data.</param>
        protected override void OnClosing(CancelEventArgs e)
        {
            if (CheckSaved() == System.Windows.Forms.DialogResult.Cancel)
            {
                e.Cancel = true;
            }
            base.OnClosing(e);
        }

        private void RefreshVPN()
        {
            var gateway = GetVPNGateway();
            if (string.IsNullOrEmpty(gateway))
            {
                txtInterface.Text = "尚未连接VPN";
                EnableFunctions = false;
            }
            else
            {
                txtInterface.Text = gateway;
                EnableFunctions = true;
            }
        }

        private string GetVPNGateway()
        {
            var entries = GetRouteEntries();
            if (entries.Count > 0)
            {
                return entries[0].Interface;
            }
            else
            {
                return string.Empty;
            }
        }

        private IList<RouteEntry> GetRouteEntries()
        {
            ProcessStartInfo startInfo = new ProcessStartInfo(ROUTE_PATH, "print");
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;
            startInfo.CreateNoWindow = true;
            var process = Process.Start(startInfo);
            string routePrint = process.StandardOutput.ReadToEnd();

            Regex rgx = new Regex(RGX_ROUTE_ENTRIES, RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
            var matches = rgx.Matches(routePrint);
            if (matches.Count < 50)
            {
                //尚未连接VPN
                return new List<RouteEntry>();
            }

            //var vpnEntries = (from match in matches.Cast<Match>()
            //                    group new RouteEntry()
            //                    {
            //                        NetworkDestination = match.Groups["Destination"].Value,
            //                        Netmask = match.Groups["Netmask"].Value,
            //                        Geteway = match.Groups["Gateway"].Value,
            //                        Interface = match.Groups["Interface"].Value,
            //                        Metric = Convert.ToInt32(match.Groups["Metric"].Value)
            //                    } by match.Groups["Interface"].Value into entries
            //                    orderby entries.Count() descending
            //                    select entries).First();

            //return vpnEntries.ToList();

            Dictionary<string, List<RouteEntry>> dicEntries = new Dictionary<string, List<RouteEntry>>();
            foreach (Match match in matches)
            {
                string strInterface = match.Groups["Interface"].Value;
                List<RouteEntry> entryList;
                if (dicEntries.ContainsKey(strInterface))
                {
                    entryList = dicEntries[strInterface];
                }
                else
                {
                    entryList = new List<RouteEntry>();
                    dicEntries.Add(strInterface, entryList);
                }

                entryList.Add(new RouteEntry()
                {
                    NetworkDestination = match.Groups["Destination"].Value,
                    Netmask = match.Groups["Netmask"].Value,
                    Geteway = match.Groups["Gateway"].Value,
                    Interface = strInterface,
                    Metric = Convert.ToInt32(match.Groups["Metric"].Value)
                });
            }

            List<RouteEntry> bigestList = new List<RouteEntry>();
            foreach (var entryList in dicEntries.Values)
            {
                if (bigestList.Count < entryList.Count)
                {
                    bigestList = entryList;
                }
            }

            return bigestList;
        }

        /// <summary>
        /// Checks if current domain list is saved.
        /// </summary>
        /// <param name="cancelled"><c>true</c>, if user cancelled current operation.</param>
        /// <returns><c>true</c>, if user saved domain list. <c>false</c>, if user discarded domain list.</returns>
        DialogResult CheckSaved()
        {
            DialogResult dialogResult = System.Windows.Forms.DialogResult.Yes;
            if (_isModified)
            {
                dialogResult = MessageBox.Show("Do you want to save current domain list?", "Route Helper", MessageBoxButtons.YesNoCancel);

                if (dialogResult == System.Windows.Forms.DialogResult.Yes)
                {
                    SaveFile();
                }
            }
            return dialogResult;
        }

        /// <summary>
        /// Saves the file. Use current opend file as the saving path. If there is not a opend file, a SaveFileDialog will be shown.
        /// </summary>
        void SaveFile()
        {
            this.SaveFile(this._filePath);
        }

        /// <summary>
        /// Saves the file to specified path. If specified file path is null or empty, a SaveFileDialog will be shown.
        /// </summary>
        /// <param name="filePath">Specified file path.</param>
        void SaveFile(string filePath)
        {
            DialogResult dialogResult = System.Windows.Forms.DialogResult.OK;
            if (string.IsNullOrEmpty(filePath))
            {
                dialogResult = fdgSave.ShowDialog();
                if (dialogResult == System.Windows.Forms.DialogResult.OK)
                {
                    filePath = fdgSave.FileName;
                }
            }
            if (dialogResult == System.Windows.Forms.DialogResult.OK)
            {
                File.WriteAllText(filePath, this.txtWebsite.Text, Encoding.Unicode);
                this._isModified = false;
                this._filePath = filePath;
            }
        }

        void OpenFile()
        {
            if (CheckSaved() != System.Windows.Forms.DialogResult.Cancel)
            {
                if (fdgOpen.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    using (StreamReader reader = new StreamReader(fdgOpen.OpenFile()))
                    {
                        this.txtWebsite.Text = reader.ReadToEnd();
                        this._isModified = false;
                        this._filePath = fdgOpen.FileName;
                    }
                }
            }
        }

        void AddRoute()
        {
            try
            {
                string[] domainList = null;
                this.Invoke(new Invokee(delegate()
                {
                    this.txtWebsite.ReadOnly = true;
                    pgbAdding.Minimum = 0;
                    pgbAdding.Maximum = txtWebsite.Lines.Length;
                    pgbAdding.Value = 0;
                    domainList = this.txtWebsite.Lines;
                    this.btnRoute.Enabled = false;
                }));

                var gateway = this.GetVPNGateway();
                if (string.IsNullOrEmpty(gateway))
                {
                    string message = "Cannot get VPN gateway. Please check the VPN connection.";
                    MessageBox.Show(message);
                    WriteLog(message);
                    WriteLog(Environment.NewLine);
                    return;
                }

                ProcessStartInfo startInfo = new ProcessStartInfo(ROUTE_PATH);
                startInfo.UseShellExecute = false;
                startInfo.RedirectStandardError = true;
                startInfo.RedirectStandardOutput = true;
                startInfo.CreateNoWindow = true;

                foreach (var line in domainList)
                {
                    string domain = line.Trim();
                    if (domain.Length > 0)
                    {
                        WriteLog("Domain:{0}", domain);
                        WriteLog(Environment.NewLine);

                        var host = System.Net.Dns.GetHostEntry(domain);
                        foreach (var ip in host.AddressList)
                        {
                            string ipRange = ip.ToString();
                            ipRange = ipRange.Remove(ipRange.LastIndexOf('.') + 1) + "0";
                            startInfo.Arguments = string.Format("add {0} mask {1} {2}",
                                ipRange,
                                "255.255.255.0",
                                gateway);

                            WriteLog("\tRoute added: {0} ({1})", ip.ToString(), ipRange);
                            WriteLog(Environment.NewLine);

                            var process = Process.Start(startInfo);
                            if (!process.StandardError.EndOfStream)
                            {
                                WriteLog(process.StandardError.ReadToEnd());
                                //WriteLog(Environment.NewLine);
                            }
                            else
                            {
                                Debug.Write(process.StandardOutput.ReadToEnd());
                            }
                        }
                        Debug.WriteLine("[-------------------------]");
                        WriteLog("-------------------------");
                        WriteLog(Environment.NewLine);
                    }

                    this.BeginInvoke(new Invokee(delegate()
                    {
                        pgbAdding.PerformStep();
                    }));
                }
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
                WriteLog(ex.ToString());
                WriteLog(Environment.NewLine);
            }
            finally
            {
                this.BeginInvoke(new Invokee(delegate()
                {
                    this.txtWebsite.ReadOnly = false;
                    this.btnRoute.Enabled = true;
                }));
            }
        }

        void WriteLog(string format, params string[] args)
        {
            WriteLog(string.Format(format, args));
        }

        void WriteLog(string text)
        {
            this.BeginInvoke(new Invokee(delegate()
            {
                this.txtLog.AppendText(text);
            }));
        }

        private void frmRouteHelper_Load(object sender, EventArgs e)
        {
            RefreshVPN();
        }

        private void btnRoute_Click(object sender, EventArgs e)
        {
            Thread addRoute = new Thread(new ThreadStart(AddRoute));
            addRoute.Start();
        }

        private void mnuExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void txtWebsite_TextChanged(object sender, EventArgs e)
        {
            this._isModified = true;
        }

        private void mnuOpen_Click(object sender, EventArgs e)
        {
            OpenFile();
        }

        private void mnuSaveAs_Click(object sender, EventArgs e)
        {
            SaveFile(null);
        }

        private void mnuSave_Click(object sender, EventArgs e)
        {
            SaveFile();
        }

        private void mnuRefresh_Click(object sender, EventArgs e)
        {
            RefreshVPN();
        }
    }
}